﻿using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework.Graphics;
using Nate_sRoguelikeGameXNA.Levels;

namespace Nate_sRoguelikeGameXNA
{
    class Pathfinder
    {
        # region Fields

        private Level _level;
        MapBlock[] map;
        HashSet<MapBlock> _closedList = new HashSet<MapBlock>();
        List<MapBlockPathInfo> _closedPathingInfo = new List<MapBlockPathInfo>();
        HashSet<MapBlock> _openBlocks = new HashSet<MapBlock>();
        List<MapBlockPathInfo> _openBlockPathingInfo = new List<MapBlockPathInfo>();
        List<MapBlockPathInfo> _path = new List<MapBlockPathInfo>();
        Dictionary<MapBlock, MapBlockPathInfo> _blockToPathingInfo = new Dictionary<MapBlock, MapBlockPathInfo>();
        MapBlock _currentBlock, _startingBlock, _endingBlock;
        private GameCharacter _player;
        MapBlockPathInfo _currentPathingInfo;
        int _iteration = 0;
        # endregion

        # region Constructor

        public Pathfinder(Level level)
        {
            _level = level;
            
        }

        # endregion

        # region Methods

        private void Advance()
        {
            _iteration++;
            if (_currentPathingInfo.MapBlock == _endingBlock)
            {
                return;
            }
            // Add the current block to the closed list
            _closedList.Add(_currentBlock);

            // Remove it from the open lists
            _openBlocks.Remove(_currentBlock);
            _openBlockPathingInfo.Remove(_currentPathingInfo);
            _closedPathingInfo.Add(_currentPathingInfo);

            // Get the surrounding blocks

            HashSet<MapBlock> currentList = new HashSet<MapBlock>(_level.GetSurroundingPassableMapBlocks(_player));
            // Remove blocks we've already inspected

            currentList.ExceptWith(_closedList);
            // Remove blocks that are already in the inspection list
            HashSet<MapBlock> openAdjacentBlocks = new HashSet<MapBlock>(currentList);
            openAdjacentBlocks.IntersectWith(_openBlocks);
            currentList.ExceptWith(_openBlocks);

            bool resortCollection = false;
            foreach (
                MapBlockPathInfo p in
                    openAdjacentBlocks.Select(b => _blockToPathingInfo[b]).Where(
                        p =>
                        _currentPathingInfo.DistanceFromStart +
                        p.CalculateDistanceFromAdjacentBlock(_currentPathingInfo.MapBlock) < p.DistanceFromStart))
            {
                p.Parent = _currentPathingInfo;
                p.CalculateDistances();
                resortCollection = true;
            }

            if (!resortCollection)
                resortCollection = currentList.Any();
            // Add the blocks to the inspection list
            _openBlocks.UnionWith(currentList);
            // Get the pathing info for the blocks
            foreach (MapBlock b in currentList)
            {
                MapBlockPathInfo info = new MapBlockPathInfo(b, _currentPathingInfo, _endingBlock, _iteration);
                _openBlockPathingInfo.Add(info);
                _blockToPathingInfo.Add(b, info);
            }
            // Sort the open lists by distance
            if (resortCollection)
                _openBlockPathingInfo.Sort();

            _currentPathingInfo = _openBlockPathingInfo[0];
            _currentBlock = _currentPathingInfo.MapBlock;
        }

        public List<MapBlockPathInfo> FindPath(GameCharacter player, MapBlock endingBlock)
        {
            _startingBlock = player.MapBlock;
            _player = player;
            _endingBlock = endingBlock;
            if (_currentPathingInfo == null || HasFoundPath())
                Reset();
            while (!HasFoundPath())
                Advance();

            return _path;
        }

        public bool HasFoundPath()
        {
            return _currentPathingInfo != null && _currentPathingInfo.MapBlock == _endingBlock;
        }

        public void Reset()
        {
            _closedList = new HashSet<MapBlock>();
            _closedPathingInfo = new List<MapBlockPathInfo>();
            _openBlocks = new HashSet<MapBlock>();
            _openBlockPathingInfo = new List<MapBlockPathInfo>();
            _path = new List<MapBlockPathInfo>();
            _blockToPathingInfo = new Dictionary<MapBlock, MapBlockPathInfo>();
            _currentBlock = _startingBlock;
            _currentPathingInfo = new MapBlockPathInfo(_currentBlock, null, _endingBlock, 0);
            _openBlockPathingInfo.Add(_currentPathingInfo);
            _path.Add(_currentPathingInfo);
            
            //endingBlock.BlockColor = Color.pink;
            _iteration = 0;
        }

        public void SimplifyPath()
        {
            throw new NotImplementedException();
        }
        # endregion
    }
}
